package pkmn;

import pkmn.attributes.Attributes;
import java.io.File;
import java.util.List;
import java.util.Scanner;
import org.jdom.Document;
import org.jdom.Element;
import org.jdom.input.SAXBuilder;
import pkmn.attributes.IvAttributes;
import pkmn.exception.DeathException;
import pkmn.move.Move;
import pkmn.nature.Nature;
import pkmn.status.Status;
import pkmn.status.StatusEnum;
import pkmn.type.Type;
import pkmn.util.Util;

public final class Pokemon {

    private String id;
    private String name;
    private String nickname;
    private int life;
    private int maxLife;
    private int exp;
    private int level;
    private int nextLevel;
    private Genre genre;
    private Attributes attributes;
    private Attributes baseAttributes;
    private int baseExp;
    private Type[] types;
    private Status status;
    private Move[] moves;
    private Nature nature;
    private IvAttributes iv;
    private int ev;
    private String trainerID;

    /**
     * Constructor. This function read a XML file with the configurations
     * @param id String
     * @param level int
     * @throws Exception
     */
    @SuppressWarnings(value = "unchecked")
    public Pokemon(String id, int level) throws Exception {
        this.id = id;
        this.status = new Status(StatusEnum.none);
        this.level = level;
        this.expCalc();
        this.nextLevelCalc();
        this.nature = Nature.random();
        this.iv = new IvAttributes();
        this.ev = this.level;

        //configurations from xml
        File xmlfile = new File("src/xml/" + id + ".xml");
        Document d = new SAXBuilder().build(xmlfile);
        Element root = d.getRootElement();

        //check if xml is valid
        if (!root.getAttribute("id").getValue().equals(id)) {
            throw new Exception("Invalid XML configuration.");
        }

        //setting name
        this.name = root.getChildText("name");

        //setting types
        List<Element> listTypes = root.getChild("types").getChildren();
        int index = 0;
        this.types = new Type[listTypes.size()];
        for (Element e : listTypes) {
            this.types[index++] = Type.newByName(e.getText());
        }

        //setting genre
        this.genre = Genre.generate(root.getChildText("genre"));

        //setting evolve type

        //setting life and attributes
        Element XMLAttributes = root.getChild("attributes");
        //hp
        calculeHP(Integer.parseInt(XMLAttributes.getChildText("hp")));
        this.life = this.maxLife;
        //
        this.baseAttributes = new Attributes(Integer.parseInt(XMLAttributes.getChildText("attack")),
                                             Integer.parseInt(XMLAttributes.getChildText("defense")),
                                             Integer.parseInt(XMLAttributes.getChildText("spAttack")),
                                             Integer.parseInt(XMLAttributes.getChildText("spDefense")),
                                             Integer.parseInt(XMLAttributes.getChildText("speed")));
        this.calculeAttrib();

        this.baseExp = Integer.parseInt(XMLAttributes.getChildText("exp"));
        //setting moves
        this.moves = new Move[4];
        List<Element> listMoves = root.getChild("moves").getChildren();
        int movePos = 3;
        for (int i = listMoves.size() - 1; i >= 0; i--) {
            if (Integer.parseInt(listMoves.get(i).getAttributeValue("level")) <= this.level) {
                moves[movePos] = new Move(listMoves.get(i).getText());
                movePos--;
            }
            if (movePos < 0) {
                break;
            }
        }
        //if no attack in the level, get the first attack
        if (moves[3] == null) {
            moves[movePos] = new Move(listMoves.get(0).getText());
        }
        Move.shift(moves);
    }

    /**
     * Return experience points after battle
     * @return int
     */
    public int getExpBattle(){
        int a = 1;
        int t = 1;
        int b = this.baseExp;
        int e = 1;
        int L = this.level;
        return a * t * b * e * L / 7;
    }

    /**
     * Calcule the HP
     * @param base int
     */
    private void calculeHP(int base) {
        this.maxLife = (this.iv.getHP() + (2 * base) + (this.ev / 4) + 100) * this.level / 100 + 10;
    }

    /**
     * Step from calculeAttrib()
     * @param base int
     * @param value int
     * @return int
     */
    private double calculeAttribStep(double base, double value) {
        return (value + (2 * base) + (this.ev / 4)) * this.level / 100 + 5;
    }

    /**
     * Calcule the attributes
     * @param attack int
     * @param defense int
     * @param spAttack int
     * @param spDefense int
     * @param speed int
     */
    private void calculeAttrib() {
        this.attributes = new Attributes(calculeAttribStep(this.baseAttributes.attack, this.iv.getAttack()),
                                         calculeAttribStep(this.baseAttributes.defense, this.iv.getDefense()),
                                         calculeAttribStep(this.baseAttributes.spAttack, this.iv.getSpAttack()),
                                         calculeAttribStep(this.baseAttributes.spDefense, this.iv.getSpDefense()),
                                         calculeAttribStep(this.baseAttributes.speed, this.iv.getSpeed()));
        this.attributes.adjustByNature(this.nature);
    }

    private void refreshAttrib(){
        //life
        int newMaxLife = (this.iv.getHP() + (2 * this.iv.getHP()) + (this.ev / 4) + 100) * this.level / 100 + 10;
        //add diff in the life
        this.addLife(newMaxLife - this.maxLife);
        if (this.status.equals(StatusEnum.fainted))
            this.removeStatus();
        this.maxLife = newMaxLife;

        //attributes
        calculeAttrib();
    }

    /**
     * Return the id
     * @return String
     */
    public String getId() {
        return this.id;
    }

    /**
     * Get the Pokémon name
     * @return String
     */
    public String getName() {
        return this.name;
    }

    /**
     * Get the actual life
     * @return int
     */
    public int getLife() {
        return this.life;
    }

    /**
     * Add life
     * @param life
     */
    public void addLife(int life) {
        this.life = Util.min(this.life + life, this.maxLife);
    }

    /**
     * Get the maximum life
     * @return int
     */
    public int getMaxLife() {
        return this.maxLife;
    }

    /**
     * Get the experience points
     * @return int
     */
    public int getExp() {
        return this.exp;
    }

    /**
     * Get level
     * @return int
     */
    public int getLevel() {
        return this.level;
    }

    /**
     * Get the genre
     * @return Genre
     */
    public Genre getGenre() {
        return this.genre;
    }

    /**
     * Get one of types
     * @param index int
     * @return Type
     */
    public Type getType(int index) {
        return this.types[index];
    }

    /**
     * Get all types
     * @return Type[]
     */
    public Type[] getTypes() {
        return this.types;
    }

    /**
     * Get status
     * @return Status
     */
    public Status getStatus() {
        return this.status;
    }

    /**
     * Set the status. Failed if pokémon have another status.
     * @param status Status
     */
    public void setStatus(Status status) {
        if (!this.status.equals(StatusEnum.none)) {
            System.out.println("But, it failed!");
        } else {
            this.status = status;
        }
    }

    /**
     * Clear status
     */
    public void removeStatus() {
        this.status = new Status();
    }

    /**
     * Get attributes
     * @return Attributes
     */
    public Attributes getAttributes() {
        return this.attributes;
    }

    /**
     * Calcule the exp points from level
     */
    public void expCalc() {
        //level ^ 3; medium-fast
        this.exp = (int)Math.pow(this.level, 3);
    }

    /**
     * Calcule the next level from level
     */
    protected void nextLevelCalc() {
        //level ^ 3; medium-fast
        this.nextLevel = (int)(Math.pow(this.level + 1, 3));// - Math.pow(this.level, 3));
    }

    /**
     * Add experience points
     * @param exp
     */
    public void addExp(int exp) {
        this.exp += exp;
        while (this.exp >= this.nextLevel) {
            this.levelUp();
        }
    }

    /**
     * Level up
     */
    public void levelUp(boolean incEV) {
        try {
            this.level++;
            this.ev++;
            if (incEV)
                this.ev++;
            this.nextLevelCalc();
            this.refreshAttrib();
            Util.printAndWait(this.name + " level up from level " + this.level + "!");
            this.updateMoves();
        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }

    public void levelUp() {
        this.levelUp(false);
    }


    /**
     * Get name of types
     * @return String
     */
    private String getTypesName() {
        String ret = "";
        if (this.types == null) {
            return ret;
        }
        for (int i = 0; i < this.types.length; i++) {
            if (!ret.equals("")) {
                ret += ", ";
            }
            ret += this.types[i].getClass().getSimpleName();
        }
        return ret;
    }

    /**
     * Get a move
     * @param index int
     * @return Move
     */
    public Move getMove(int index) {
        return this.moves[index];
    }

    /**
     * Get all moves
     * @return Move[]
     */
    public Move[] getMove() {
        return this.moves;
    }

    /**
     * Get name of moves
     * @return String
     */
    private String getMovesName() {
        String ret = "";
        if (this.moves == null) {
            return ret;
        }
        for (int i = 0; i < this.moves.length; i++) {
            if (!ret.equals("")) {
                ret += "\n";
            }
            ret += "-Move " + (i + 1) + ": " + this.moves[i];
        }
        return ret;
    }

    /**
     * Get the values of attributes
     * @return
     */
    private String getAttributesValues() {
        return "-Attack: " + Integer.toString((int) attributes.attack)
                + "\n-Deffense: " + Integer.toString((int) attributes.defense)
                + "\n-Sp. Attack: " + Integer.toString((int) attributes.spAttack)
                + "\n-Sp. Deffense: " + Integer.toString((int) attributes.spDefense)
                + "\n-Speed: " + Integer.toString((int) attributes.speed);
    }

    /**
     * Increase the EV.
     */
    public void incEV() {
        if (this.ev < 256) {
            this.ev++;
        }
    }

    public void receiveDamage(int damage) throws DeathException {
        if (damage >= this.life) {
            this.life = 0;
            this.status = new Status(StatusEnum.fainted);
            throw new DeathException(this);
        } else {
            this.life -= damage;
        }
    }

    /**
     * Check if pokémon is alive.
     * @return boolean
     */
    public boolean fainted() {
        return this.status.equals(StatusEnum.fainted);
    }

    /**
     * Restore HP to max and remove status.
     */
    public void fullRestore() {
        this.life = this.maxLife;
        this.status = new Status(StatusEnum.none);
    }

    /**
     * Set the trainer ID
     * @param id The id from trainer.
     */
    public void setTrainerID(String id){
        this.trainerID = id;
    }

    /**
     * Get the trainer ID
     * @return The id from trainer.
     */
    public String getTrainerID(){
        return this.trainerID;
    }

    /**
     * Update the moves in level up.
     * @throws Exception
     */
    @SuppressWarnings(value = "unchecked")
    private void updateMoves() throws Exception {
        File xmlfile = new File("src/xml/" + this.id + ".xml");
        Document d = new SAXBuilder().build(xmlfile);
        Element root = d.getRootElement();
        List<Element> listMoves = root.getChild("moves").getChildren();
        for (Element e : listMoves) {
            int movePos = 0;
            int levelMove = Integer.parseInt(e.getAttributeValue("level"));
            if (levelMove > this.level) {
                return;
            }
            if (levelMove == this.level) {
                String moveName = e.getText();
                //check if pokémon know the move
                for (int k = 0; k < 4 && this.moves[k] != null; k++) {
                    if (this.moves[k].toString().equals(moveName)) {
                        return;
                    }
                }
                //search a empty slot
                while (this.moves[movePos] != null) {
                    movePos++;
                    if (movePos > 3) {
                        //no slot free
                        int teach = 0;
                        Scanner s = new Scanner(System.in);
                        while (true) {
                            Util.printAndWait(this.name + " wants to learn the move " + moveName + ".");
                            Util.printAndWait("However, " + this.name + " already knows four moves.");
                            Util.printAndWait("Should a move be deleted and replaced with " + moveName + "?");
                            System.out.print("(1- Yes, 2- No): ");
                            int i = s.nextInt();
                            if (i == 1) {
                                System.out.println("Which move shold be forgotten?");
                                int k;
                                for (k = 0; k < 4; k++) {
                                    if (this.moves[k] != null) {
                                        System.out.println((k + 1) + "- " + this.moves[k]);
                                    }
                                }
                                movePos = s.nextInt() - 1;
                                Util.printAndWait("1, 2, and ... ... ... Poof!");
                                Util.printAndWait(this.name + " forgot " + this.moves[movePos]);
                                Util.printAndWait("And...");

                            } else {
                                Util.printAndWait("Stop trying to teach " + moveName + "?");
                                System.out.print("(1- Yes, 2- No): ");
                                teach = s.nextInt();
                            }
                            if (teach == 1) {
                                Util.printAndWait(this.name + " did not learn the move " + moveName + ".");
                                return;
                            }
                            break;
                        }
                        break;
                    }
                }
                moves[movePos] = new Move(moveName);
                Util.printAndWait(this.name + " learned " + moveName + "!");
            }
        }
    }

    /**
     * Not implemented
     */
    public void evolve() {
        String oldName = this.name;
        Util.printAndWait("What?! " + oldName + " is evolving!");
        System.out.print("Accept? (1- Yes; 2- No):");
        /*int option = new Scanner(System.in).nextInt();
        if (option == 1){

        //Reload status

        Util.printAndWait(  "Congratulations! Your " + oldName
        + " evolve into " + this.name + "!");
        }else*/
        Util.printAndWait(oldName + " stop the evolution.");
    }
    
    public String getNickname(){
        return this.nickname;
    }

    public void setNickname(String nickname){
        this.nickname = nickname;
    }

    @Override
    public String toString() {
        return "Name: " + this.name
                + this.genre
                + "\nType: " + this.getTypesName()
                + "\nLife: " + this.life + "/" + this.maxLife
                + "\nLevel: " + this.level
                + "\nExp: " + this.exp
                + "\nNext Level: " + this.nextLevel
                + "\nAtributes:\n" + this.getAttributesValues()
                + "\nStatus: " + this.status
                + "\nMoves:\n" + this.getMovesName()
                + "\nNature: " + this.nature;
    }
}
